//********************************************************************
//	created:	13:2:2012   21:20
//	filename: 	xmlrpcvalue.cpp
//	author:		tiamo
//	purpose:	xmlrpc value
//********************************************************************

#include "stdafx.h"
#include "xmlrpcvalue.h"

//
// xmlrpc
//
namespace xmlrpc
{
	//
	// get i4
	//
	int32_t Value::getI4() const
	{
		typeCheck(TypeI4);
		return *static_cast<I4Value const*>(this);
	}

	//
	// get i8
	//
	int64_t Value::getI8() const
	{
		typeCheck(TypeI8);
		return *static_cast<I8Value const*>(this);
	}

	//
	// get boolean
	//
	bool Value::getBoolean() const
	{
		typeCheck(TypeBoolean);
		return *static_cast<BooleanValue const*>(this);
	}

	//
	// get double
	//
	double Value::getDouble() const
	{
		typeCheck(TypeDouble);
		return *static_cast<DoubleValue const*>(this);
	}

	//
	// get string
	//
	UniString const& Value::getString() const
	{
		typeCheck(TypeString);
		return *static_cast<StringValue const*>(this);
	}

	//
	// get count
	//
	uint32_t Value::getCount() const
	{
		typeCheck(TypeArray);
		return static_cast<uint32_t>(static_cast<ArrayValue const*>(this)->getArray().size());
	}

	//
	// get
	//
	Value const* Value::get(uint32_t index) const
	{
		if(index >= getCount())
			ThrowXmlRpcException(Exception::IndexError, L"invalid index (%u < %u)", index, getCount());

		return static_cast<ArrayValue const*>(this)->getArray()[index].get();
	}

	//
	// has
	//
	Value const* Value::has(UniString const& key) const
	{
		typeCheck(TypeStruct);
		StructValue::ValueType const& valueList								= static_cast<StructValue const*>(this)->getStruct();
		auto it																= valueList.find(key);
		if(it == valueList.end())
			return nullptr;

		return it->second;
	}

	//
	// get
	//
	Value const* Value::get(UniString const& key) const
	{
		Value const* retValue												= has(key);
		if(!retValue)
			ThrowXmlRpcException(Exception::IndexError, L"invalid key (%ls)", key.c_str());

		return retValue;
	}

	//
	// encode
	//
	UniString I4Value::encode() const
	{
		wchar_t buffer[100]													= {0};
		platform::snwprintf(buffer, ARRAYSIZE(buffer), L"<i4>%d</i4>", mI4Value);
		return buffer;
	}

	//
	// encode
	//
	UniString I8Value::encode() const
	{
		wchar_t buffer[100]													= {0};
		platform::snwprintf(buffer, ARRAYSIZE(buffer), L"<i8>%lld</i8>", mI8Value);
		return buffer;
	}

	//
	// encode
	//
	UniString DoubleValue::encode() const
	{
		wchar_t buffer[100]													= {0};
		platform::snwprintf(buffer, ARRAYSIZE(buffer), L"<double>%f</double>", mDoubleValue);
		return buffer;
	}

	//
	// encode
	//
	UniString DateTimeValue::encode() const
	{
		UniString retValue													= L"<dateTime.iso8601>";
		wchar_t buffer[100]													= {0};
		platform::snwprintf(buffer, ARRAYSIZE(buffer), L"%u%02u%02uT%02u:%02u:%02u", mDateTime.mYear, mDateTime.mMonth, mDateTime.mDay, mDateTime.mHour, mDateTime.mMinute, mDateTime.mSecond);
		retValue															+= buffer;
		if(mDateTime.mMicroSecond)
		{
			platform::snwprintf(buffer, ARRAYSIZE(buffer), L".%06u", mDateTime.mMicroSecond);
			retValue														+= buffer;
		}
		retValue															+= L"</dateTime.iso8601>";
		return retValue;
	}

	//
	// encode
	//
	UniString ByteBufferValue::encode() const
	{
		return L"<base64>" + base64::encode(mDataBuffer.getBuffer(), mDataBuffer.getLength()) + L"/<base64>";
	}

	//
	// encode
	//
	UniString ArrayValue::encode() const
	{
		UniString retValue													= L"<array><data>";
		for(auto it = mArrayValue.begin(); it != mArrayValue.end(); ++ it)
			retValue														+= L"<value>" + (*it)->encode() + L"</value>";
		retValue															+= L"</data></array>";

		return retValue;
	}

	//
	// encode
	//
	UniString StructValue::encode() const
	{
		UniString retValue													= L"<struct>";
		for(auto it = mStructValue.begin(); it != mStructValue.end(); ++ it)
		{
			retValue														+= L"<member><name>" + utils::escapeXml(it->first) + L"</name>";
			retValue														+= L"<value>" + it->second->encode() + L"</value></member>";
		}
		retValue															+= L"</struct>";

		return retValue;
	}

	//
	// encode response
	//
	Utf8String Encoder::encodeResponse(Value const* response)
	{
		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><methodResponse><params><param><value>" + encodeValue(response) + "</value></param></params></methodResponse>";
	}

	//
	// encode fault
	//
	Utf8String Encoder::encodeFault(Value const*  fault)
	{
		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><methodResponse><fault><value>" + encodeValue(fault) + "</value></fault></methodResponse>";
	}

	//
	// decode
	//
	void Decoder::decodeRequest(utils::DataBuffer&& xmlContent)
	{
		//
		// parse xml content
		//
		utils::XmlParser xmlParser;
		xmlParser.parse(std::move(xmlContent), false);

		//
		// decode method name and param list
		//
		mMethodName															= xmlParser.getString(L"/methodCall/methodName");
		xmlNodePtr paramArrayNode											= xmlParser.getNode(L"/methodCall/params");
		mParamList															= convertParamArray(xmlParser, paramArrayNode);
	}

#define CHECK_NODE_NAME(node, name)	do{UniString nodeName = xmlParser.getNodeName(node); if(nodeName != (name)) ThrowXmlRpcException(Exception::ParseError, L"Invalid node name (%ls != %ls)", nodeName.c_str(), (name));}while(0)
#define CHECK_CHILDREN_COUNT(node, count) do{uint32_t childrenCount = xmlParser.getChildrenCount(node); if(childrenCount != (count))ThrowXmlRpcException(Exception::ParseError, L"Invalid children count (%u != %u)", childrenCount, (count));}while(0)

	//
	// parse int32
	//
	utils::SmartPointer<Value> Decoder::parseInt32(UniString const& valueContent)
	{
		//
		// check empty
		//
		if(valueContent.empty())
			ThrowXmlRpcException(Exception::ParseError, L"<int> element is empty");

		//
		// check space
		//
		if(iswspace(valueContent[0]))
			ThrowXmlRpcException(Exception::ParseError, L"<int> element(%ls) starts with space", valueContent.c_str());

		//
		// convert to long
		//
		errno																= 0;
		wchar_t* end														= nullptr;
		long temp															= wcstol(valueContent.c_str(), &end, 0);

		//
		// range error
		//
		if(errno == ERANGE || temp > std::numeric_limits<int32_t>::max() || temp < std::numeric_limits<int32_t>::min())
			ThrowXmlRpcException(Exception::ParseError, L"<int> element(%ls) exceeds int's range(%d - %d)", valueContent.c_str(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max());

		//
		// unknown error
		//
		if(errno)
			ThrowXmlRpcException(Exception::ParseError, L"<int> element(%ls) unexpected parse error (%d)", valueContent.c_str(), errno);

		//
		// check tail
		//
		if(*end)
			ThrowXmlRpcException(Exception::ParseError, L"<int> element(%ls) contains junk(%ls)", valueContent.c_str(), end);

		//
		// create i4 value
		//
		return new I4Value(static_cast<int32_t>(temp));
	}

	//
	// parse int64
	//
	utils::SmartPointer<Value> Decoder::parseInt64(UniString const& valueContent)
	{
		//
		// check empty
		//
		if(valueContent.empty())
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element is empty");

		//
		// check space
		//
		if(iswspace(valueContent[0]))
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element(%ls) starts with space", valueContent.c_str());

		//
		// convert to long
		//
		errno																= 0;
		wchar_t* end														= nullptr;
		int64_t temp														= platform::wcstoi64(valueContent.c_str(), &end, 0);

		//
		// range error
		//
		if(errno == ERANGE || temp > std::numeric_limits<int64_t>::max() || temp < std::numeric_limits<int64_t>::min())
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element(%ls) exceeds int's range(%lld - %lld)", valueContent.c_str(), std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());

		//
		// unknown error
		//
		if(errno)
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element(%ls) unexpected parse error (%d)", valueContent.c_str(), errno);

		//
		// check tail
		//
		if(*end)
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element(%ls) contains junk(%ls)", valueContent.c_str(), end);

		//
		// create i8 value
		//
		return new I8Value(temp);
	}

	//
	// parse boolean
	//
	utils::SmartPointer<Value> Decoder::parseBoolean(UniString const& valueContent)
	{
		bool isOne															= valueContent == L"1";
		bool isZero															= valueContent == L"0";
		if(isOne)
			return new BooleanValue(true);

		if(isZero)
			return new BooleanValue(false);

		ThrowXmlRpcException(Exception::ParseError, L"<boolean> element(%ls) must be 0 or 1", valueContent.c_str());
	}

	//
	// parse double
	//
	utils::SmartPointer<Value> Decoder::parseDouble(UniString const& valueContent)
	{
		//
		// check empty
		//
		if(valueContent.empty())
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element is empty");

		//
		// check space
		//
		if(iswspace(valueContent[0]))
			ThrowXmlRpcException(Exception::ParseError, L"<i8> element(%ls) starts with space", valueContent.c_str());

		//
		// convert to double
		//
		errno																= 0;
		wchar_t* end														= nullptr;
		double temp															= wcstod(valueContent.c_str(), &end);

		//
		// range check
		//
		if(errno == ERANGE || temp > std::numeric_limits<double>::max() || temp < std::numeric_limits<double>::min())
			ThrowXmlRpcException(Exception::ParseError, L"<double> element(%ls) exceeds int's range(%f - %f)", valueContent.c_str(), std::numeric_limits<double>::min(), std::numeric_limits<double>::max());

		//
		// unknown error
		//
		if(errno)
			ThrowXmlRpcException(Exception::ParseError, L"<double> element(%ls) unexpected parse error (%d)", valueContent.c_str(), errno);

		//
		// check tail
		//
		if(*end)
			ThrowXmlRpcException(Exception::ParseError, L"<double> element(%ls) contains junk(%ls)", valueContent.c_str(), end);

		//
		// create double value
		//
		return new DoubleValue(temp);
	}

	//
	// parse datetime
	//
	utils::SmartPointer<Value> Decoder::parseDateTime(UniString const& valueContent)
	{
		if(valueContent.length() < 17)
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) length(%u < 17)", valueContent.c_str(), static_cast<uint32_t>(valueContent.length()));

		utils::SmartPointer<DateTimeValue> retValue							= new DateTimeValue;
		wchar_t temp[8]														= {0};
		uint32_t index														= 0;
		uint32_t tempValue													= 0;
		for(uint32_t i = 0; i < 8; i ++)
		{
			if(!iswdigit(valueContent[i]))
				ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) digit(%u)", valueContent.c_str(), i);

			temp[index]														= valueContent[i];
			index															+= 1;
			if(i == 3)
			{
				retValue->getDateTime().mYear								= static_cast<uint16_t>(wcstoul(temp, nullptr, 10));
				index														= 0;
				temp[2]														= 0;
			}
			else if(i == 5)
			{
				tempValue													= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
				if(tempValue < 1 || tempValue > 12)
					ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) month(%u) shoud in range of [1,12]", valueContent.c_str(), tempValue);
				retValue->getDateTime().mMonth								= static_cast<uint8_t>(tempValue);
				index														= 0;
			}
			else if(i == 7)
			{
				tempValue													= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
				if(tempValue < 1 || tempValue > 31)
					ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) day(%u) shoud in range of [1,31]", valueContent.c_str(), tempValue);
				retValue->getDateTime().mDay								= static_cast<uint8_t>(tempValue);
			}
		}
		
		temp[2]																= 0;
		if(valueContent[8] != L'T')
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(8) should be 'T'", valueContent.c_str());

		if(!iswdigit(valueContent[9]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(9) should be digit", valueContent.c_str());
		temp[0]																= valueContent[9];

		if(!iswdigit(valueContent[10]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(10) should be digit", valueContent.c_str());
		temp[1]																= valueContent[10];
		tempValue															= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
		if(tempValue > 23)
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) hour(%u) shoud in range of [0,23]", valueContent.c_str(), tempValue);
		retValue->getDateTime().mHour										= static_cast<uint8_t>(tempValue);

		if(valueContent[11] != L':')
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(11) should be ':'", valueContent.c_str());

		if(!iswdigit(valueContent[12]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(12) should be digit", valueContent.c_str());
		temp[0]																= valueContent[12];

		if(!iswdigit(valueContent[13]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(13) should be digit", valueContent.c_str());
		temp[1]																= valueContent[13];
		tempValue															= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
		if(tempValue > 59)
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) minute(%u) shoud in range of [0,59]", valueContent.c_str(), tempValue);
		retValue->getDateTime().mMinute										= static_cast<uint8_t>(tempValue);

		if(valueContent[14] != L':')
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(14) should be ':'", valueContent.c_str());

		if(!iswdigit(valueContent[15]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(15) should be digit", valueContent.c_str());
		temp[0]																= valueContent[15];

		if(!iswdigit(valueContent[16]))
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(16) should be digit", valueContent.c_str());
		temp[1]																= valueContent[16];
		tempValue															= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
		if(tempValue > 59)
			ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) second(%u) shoud in range of [0,59]", valueContent.c_str(), tempValue);
		retValue->getDateTime().mSecond										= static_cast<uint8_t>(tempValue);

		if(valueContent.length() > 17)
		{
			if(valueContent[17] != L'.')
				ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(17) should be '.'", valueContent.c_str());

			if(valueContent.length() == 18 || valueContent.length() > 24)
				ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) length", valueContent.c_str());

			temp[0]															= 0;
			temp[1]															= 0;
			uint32_t totalLength											= static_cast<uint32_t>(valueContent.length());
			for(uint32_t i = 18; i < totalLength; i ++)
			{
				if(!iswdigit(valueContent[i]))
					ThrowXmlRpcException(Exception::ParseError, L"Invalid datetime(%ls) at index(%u) should be digit", valueContent.c_str(), i);
				temp[i - 18]												= valueContent[i];
			}
			uint32_t microSecond											= static_cast<uint32_t>(wcstoul(temp, nullptr, 10));
			for(uint32_t i = 0; i < 24 - totalLength; i ++)
				microSecond													*= 10;
			retValue->getDateTime().mMicroSecond							= microSecond;
		}

		return retValue.get();
	}

	//
	// parse base64
	//
	utils::SmartPointer<Value> Decoder::parseBase64(UniString const& valueContent)
	{
		//
		// parse base64 value
		//
		utils::DataBuffer dataBuffer(static_cast<uint32_t>((valueContent.length() + 3) / 4 * 3));
		uint32_t length														= dataBuffer.getSize();
		if(base64::decode(valueContent, dataBuffer.getBuffer(), length))
			ThrowXmlRpcException(Exception::ParseError, L"<base64> element(%ls) contains invalid data", valueContent.c_str());

		//
		// create byte buffer value
		//
		return new ByteBufferValue(std::move(dataBuffer));
	}

	//
	// parse array
	//
	utils::SmartPointer<Value> Decoder::parseArray(utils::XmlParser& xmlParser, xmlNodePtr typeNode)
	{
		//
		// only one data node
		//
		CHECK_CHILDREN_COUNT(typeNode, 1);

		//
		// check data node name
		//
		xmlNodePtr dataNode													= xmlParser.getFirstChild(typeNode);
		CHECK_NODE_NAME(dataNode, L"data");

		//
		// value node
		//
		utils::SmartPointer<ArrayValue> retValue							= new ArrayValue;
		xmlNodePtr valueNode												= xmlParser.getFirstChild(dataNode);
		while(valueNode)
		{
			//
			// check name
			//
			CHECK_NODE_NAME(valueNode, L"value");

			//
			// parse and add it
			//
			retValue->getArray().push_back(parseValue(xmlParser, valueNode));

			//
			// get next value
			//
			valueNode														= xmlParser.getNextSibling(valueNode);
		}
		return retValue.get();
	}

	//
	// parse struct
	//
	utils::SmartPointer<Value> Decoder::parseStruct(utils::XmlParser& xmlParser, xmlNodePtr typeNode)
	{
		//
		// member node
		//
		utils::SmartPointer<StructValue> retValue							= new StructValue;
		xmlNodePtr memberNode												= xmlParser.getFirstChild(typeNode);
		while(memberNode)
		{
			//
			// must be a member node with (name,value) children
			//
			CHECK_NODE_NAME(memberNode, L"member");
			CHECK_CHILDREN_COUNT(memberNode, 2);

			//
			// check name
			//
			xmlNodePtr node1												= xmlParser.getFirstChild(memberNode);
			xmlNodePtr node2												= xmlParser.getNextSibling(node1);
			UniString nodeName1												= xmlParser.getNodeName(node1);
			UniString nodeName2												= xmlParser.getNodeName(node2);
			xmlNodePtr nameNode												= nullptr;
			xmlNodePtr valueNode											= nullptr;
			if(nodeName1 == L"name")
			{
				nameNode													= node1;
				if(nodeName2 == L"value")
					valueNode												= node2;
			}
			else if(nodeName2 == L"name")
			{
				nameNode													= node2;
				if(nodeName1 == L"value")
					valueNode												= node1;
			}

			//
			// check nodes
			//
			if(!nameNode || !valueNode)
				ThrowXmlRpcException(Exception::ParseError, L"<member> contains invalid children(%ls:%ls)", nodeName1.c_str(), nodeName2.c_str());

			//
			// parse and add value
			//
			retValue->getStruct()[xmlParser.getContent(nameNode)]			= parseValue(xmlParser, valueNode);

			//
			// get next member
			//
			memberNode														= xmlParser.getNextSibling(memberNode);
		}
		return retValue.get();
	}

	//
	// parse value
	//
	utils::SmartPointer<Value> Decoder::parseValue(utils::XmlParser& xmlParser, xmlNodePtr valueNode)
	{
		//
		// no type element, treat as a string
		//
		uint32_t childrenCount												= xmlParser.getChildrenCount(valueNode);
		if(!childrenCount)
			return new StringValue(xmlParser.getContent(valueNode));

		//
		// only one child (type node)
		//
		CHECK_CHILDREN_COUNT(valueNode, 1);
		xmlNodePtr typeNode													= xmlParser.getFirstChild(valueNode);
		UniString typeName													= xmlParser.getNodeName(typeNode);
		if(typeName == L"struct")
			return parseStruct(xmlParser, typeNode);

		if(typeName == L"array")
			return parseArray(xmlParser, typeNode);

		//
		// simple type node should not has any children
		//
		CHECK_CHILDREN_COUNT(typeNode, 0);

		//
		// parse int32
		//
		UniString valueContent												= xmlParser.getContent(typeNode);
		if(typeName == L"int" || typeName == L"i4" || typeName == L"i1" || typeName == L"i3" || typeName == L"ex:i1" || typeName == L"ex:i2")
			return parseInt32(valueContent);

		//
		// parse int64
		//
		if(typeName == L"i8" || typeName == L"ex:i8")
			return parseInt64(valueContent);

		//
		// parse boolean
		//
		if(typeName == L"boolean")
			return parseBoolean(valueContent);

		//
		// parse double
		//
		if(typeName == L"double")
			return parseDouble(valueContent);

		//
		// parse date time
		//
		if(typeName == L"datetime.iso8601")
			return parseDateTime(valueContent);

		//
		// parse string
		//
		if(typeName == L"string")
			return new StringValue(valueContent);

		//
		// parse base64
		//
		if(typeName == L"base64")
			return parseBase64(valueContent);

		//
		// parse nil
		//
		if(typeName == L"nil" || typeName == L"ex:nil")
			return new Value(Value::TypeNull);

		ThrowXmlRpcException(Exception::ParseError, L"Unknown value type <%ls>", typeName.c_str());
	}

	//
	// convert param array
	//
	utils::SmartPointer<ArrayValue> Decoder::convertParamArray(utils::XmlParser& xmlParser, xmlNodePtr paramArrayNode)
	{
		//
		// allow empty params, workaround for Ruby XML-RPC and old versions of xmlrpc-epi
		//
		utils::SmartPointer<ArrayValue> retValue							= new ArrayValue;
		if(!paramArrayNode)
			return retValue;

		//
		// every child should be a param node
		//
		xmlNodePtr paramNode												= xmlParser.getFirstChild(paramArrayNode);
		while(paramNode)
		{
			//
			// node name should be param
			//
			CHECK_NODE_NAME(paramNode, L"param");

			//
			// and only one child (value node)
			//
			CHECK_CHILDREN_COUNT(paramNode, 1);

			//
			// node name should be value
			//
			xmlNodePtr valueNode											= xmlParser.getFirstChild(paramNode);
			CHECK_NODE_NAME(valueNode, L"value");

			//
			// parse and add it
			//
			retValue->getArray().push_back(parseValue(xmlParser, valueNode));

			//
			// advance to the next node
			//
			paramNode														= xmlParser.getNextSibling(paramNode);
		}

		return retValue;
	}
}
